﻿using MongoDB.Bson;
using Newtonsoft.Json.Linq;
using ProductCatalog.WebApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Web;

namespace ProductCatalog.WebApi.DataLayer
{
    // This class is used to resolve impedience mismatch between the model (Product) and json (JObject).
    public static class DataHelper
    {
        // Key:   The required product properties.
        // Value: Possible matching product propeties (optional).
        private static Dictionary<string, List<string>> requiredProductProperties = null;

        static DataHelper()
        {
            // Initialize requiredProductProperties:
            requiredProductProperties = new Dictionary<string, List<string>>();

            // Reflect over the Product type during initialization to generate a 
            // list of required properties.  This list will be checked when a 
            // client attempts to insert a product to make sure these are present.
            // Optionally, we may try to derive required properties, for example,
            // extracting the name for a product from it's description.
            var productProps = typeof(Product).GetProperties();
            if (productProps != null)
                foreach (PropertyInfo pi in productProps)
                {
                    var attr = pi.GetCustomAttributes<DataMemberAttribute>(false).FirstOrDefault();
                    if ((attr != null) && (attr.IsRequired))
                    {
                        // Populate any possible matches for data we want to consider.
                        var possibleMatches = new List<string>();
                        if (string.Compare(attr.Name, "tags", true) == 0)
                            possibleMatches.Add("categories");

                        if (string.Compare(attr.Name, "prodname", true) == 0)
                        {
                            possibleMatches.Add("title");
                            possibleMatches.Add("brand");
                        }

                        if (string.Compare(attr.Name, "proddesc", true) == 0)
                            possibleMatches.Add("desc");

                        if (string.Compare(attr.Name, "prodprice", true) == 0)
                            possibleMatches.Add("price");

                        if (string.Compare(attr.Name, "imagerul", true) == 0)
                            possibleMatches.Add("image");

                        requiredProductProperties.Add(attr.Name, possibleMatches);
                    }
                }
        }

        public static bool HasRequiredProperties(this BsonDocument doc, out List<string> missingProperties)
        {
            missingProperties = new List<string>();

            foreach (string p in requiredProductProperties.Keys)
            {
                BsonValue value;
                if (!doc.TryGetValue(p, out value))
                    missingProperties.Add(p);
            }
            return (missingProperties.Count == 0);
        }

        public static bool TryAddMissingProperties(this BsonDocument doc, List<string> missingProperties)
        {
            var result = true;

            foreach (string s in missingProperties)
            {
                var possibleMatches = requiredProductProperties[s];

                var query = from e in doc.Elements
                            where possibleMatches.Contains(e.Name.ToLower())
                            select e;
                var element = query.FirstOrDefault();

                if (element != null)
                    doc.Add(s, element.Value);
                else
                {
                    result = false;
                    break;
                }
            }

            return result;
        }

        public static Product ToProduct(this BsonDocument doc)
        {
            if (doc != null)
            {
                var product = new Product();
                product.ProdId = doc["_id"].ToString();
                product.ProdName = doc["prodName"].ToString();
                product.ProdDesc = doc["prodDesc"].ToString();
                product.ProdPrice = doc["prodPrice"].ToDouble();
                product.ImageUrl = doc["imageUrl"].ToString();
                foreach (BsonValue v in doc["tags"].AsBsonArray)
                    product.Tags.Add(v.AsString);

                return product;
            }
            else
                return null;
        }

        public static JObject ToProductDetails(this BsonDocument doc, double averageRating)
        {
            // Niko
            // not pretty and might casue issues on non US-EN locale due to time serialization etc.
            // needs to check weather this is handled by driver properly
            doc.Add("prodId", doc["_id"].ToString());
            doc.Remove("_id");
            doc.Remove("reviews");
            doc.Add("avgRating", averageRating);
            return JObject.Parse(doc.ToJson());
        }

        public static Review ToReview(this BsonDocument doc)
        {

            // Niko
            var review = new Review();
            review.reviewId = doc["reviewId"].ToString();
            review.prodId = doc["prodId"].ToString();
            review.reviewDate = doc["reviewDate"].ToString();
            review.userId = doc["userId"].ToString();

            BsonValue bv;
            if (doc.TryGetValue("comments", out bv))
                review.comments = bv.ToString();
            else
                review.comments = "";

            if (doc.TryGetValue("rating", out bv))
                review.rating = bv.ToInt32();

            return review;
        }
    }
}